/*=============================================================================

  Library: CFS

  Copyright (c) German Cancer Research Center,
    Division of Medical and Biological Informatics

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

=============================================================================*/

#include "CFSPluginFrameworkListeners_p.h"

#include "CFSException.h"
#include "CFSPluginFrameworkContext_p.h"
#include "CFSPluginConstants.h"
#include "CFSLDAPExpr_p.h"
#include "CFSServiceReference_p.h"

#include <QStringListIterator>
#include <QDebug>

const int CFSPluginFrameworkListeners::OBJECTCLASS_IX = 0;
const int CFSPluginFrameworkListeners::SERVICE_ID_IX = 1;
const int CFSPluginFrameworkListeners::SERVICE_PID_IX = 2;

//----------------------------------------------------------------------------
CFSPluginFrameworkListeners::CFSPluginFrameworkListeners(CFSPluginFrameworkContext* pluginFw)
  : pluginFw(pluginFw)
{
  hashedServiceKeys << CFSPluginConstants::OBJECTCLASS.toLower()
      << CFSPluginConstants::SERVICE_ID.toLower()
      << CFSPluginConstants::SERVICE_PID.toLower();

  for (int i = 0; i < hashedServiceKeys.size(); ++i)
  {
    cache.push_back(QHash<QString, QList<CFSServiceSlotEntry> >());
  }
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::addServiceSlot(
    QSharedPointer<CFSPlugin> plugin, QObject* receiver,
    const char* slot, const QString& filter)
{
  QMutexLocker lock(&mutex); Q_UNUSED(lock)
  CFSServiceSlotEntry sse(plugin, receiver, slot, filter);
  if (serviceSet.contains(sse))
  {
    removeServiceSlot_unlocked(plugin, receiver, slot);
  }
  serviceSet.insert(sse);
  checkSimple(sse);

  connect(receiver, SIGNAL(destroyed(QObject*)), this, SLOT(serviceListenerDestroyed(QObject*)), Qt::DirectConnection);
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::removeServiceSlot(QSharedPointer<CFSPlugin> plugin,
                                                    QObject* receiver,
                                                    const char* slot)
{
  QMutexLocker lock(&mutex);
  removeServiceSlot_unlocked(plugin, receiver, slot);
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::removeServiceSlot_unlocked(QSharedPointer<CFSPlugin> plugin,
                                                             QObject* receiver,
                                                             const char* slot)
{
  CFSServiceSlotEntry entryToRemove(plugin, receiver, slot);
  QMutableSetIterator<CFSServiceSlotEntry> it(serviceSet);
  while (it.hasNext())
  {
    CFSServiceSlotEntry currentEntry = it.next();
    if (currentEntry == entryToRemove)
    {
      currentEntry.setRemoved(true);
      //listeners.framework.hooks.handleServiceListenerUnreg(sle);
      removeFromCache(currentEntry);
      it.remove();
      if (slot) break;
    }
  }

  if (plugin)
  {
    disconnect(receiver, SIGNAL(destroyed(QObject*)), this, SLOT(serviceListenerDestroyed(QObject*)));
  }
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::serviceListenerDestroyed(QObject *listener)
{
  this->removeServiceSlot(QSharedPointer<CFSPlugin>(0), listener, 0);
}

//----------------------------------------------------------------------------
QSet<CFSServiceSlotEntry> CFSPluginFrameworkListeners::getMatchingServiceSlots(
    const CFSServiceReference& sr, bool lockProps)
{
  QMutexLocker lock(&mutex); Q_UNUSED(lock);

  QSet<CFSServiceSlotEntry> set;
  set.reserve(serviceSet.size());
  // Check complicated or empty listener filters
  int n = 0;
  CFSLDAPExpr expr;
  foreach (const CFSServiceSlotEntry& sse, complicatedListeners)
  {
    ++n;
    expr = sse.getLDAPExpr();
    if (expr.isNull() || expr.evaluate(sr.d_func()->getProperties(), false))
    {
      set.insert(sse);
    }
  }

  if (pluginFw->debug.ldap)
  {
    qDebug() << "Added" << set.size() << "out of" << n
      << "listeners with complicated filters";
  }

  // Check the cache
  QStringList c = sr.d_func()->getProperty(CFSPluginConstants::OBJECTCLASS, lockProps).toStringList();
  foreach (QString objClass, c)
  {
    addToSet(set, OBJECTCLASS_IX, objClass);
  }

  bool ok = false;
  qlonglong service_id = sr.d_func()->getProperty(CFSPluginConstants::SERVICE_ID, lockProps).toLongLong(&ok);
  if (ok)
  {
    addToSet(set, SERVICE_ID_IX, QString::number(service_id));
  }

  QStringList service_pids = sr.d_func()->getProperty(CFSPluginConstants::SERVICE_PID, lockProps).toStringList();
  foreach (QString service_pid, service_pids)
  {
    addToSet(set, SERVICE_PID_IX, service_pid);
  }

  return set;
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::frameworkError(QSharedPointer<CFSPlugin> p, const CFSException& e)
{
  emit frameworkEvent(CFSPluginFrameworkEvent(CFSPluginFrameworkEvent::PLUGIN_ERROR, p, e));
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::emitFrameworkEvent(const CFSPluginFrameworkEvent& event)
{
  emit frameworkEvent(event);
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::emitPluginChanged(const CFSPluginEvent& event)
{
  emit pluginChangedDirect(event);

  if (!(event.getType() == CFSPluginEvent::STARTING ||
      event.getType() == CFSPluginEvent::STOPPING ||
      event.getType() == CFSPluginEvent::LAZY_ACTIVATION))
  {
    emit pluginChangedQueued(event);
  }
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::serviceChanged(
    const QSet<CFSServiceSlotEntry>& receivers,
    const CFSServiceEvent& evt)
{
  QSet<CFSServiceSlotEntry> matchBefore;
  serviceChanged(receivers, evt, matchBefore);
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::serviceChanged(
    const QSet<CFSServiceSlotEntry>& receivers,
    const CFSServiceEvent& evt,
    QSet<CFSServiceSlotEntry>& matchBefore)
{
  CFSServiceReference sr = evt.getServiceReference();
  //QStringList classes = sr.getProperty(CFSPluginConstants::OBJECTCLASS).toStringList();
  int n = 0;

  //framework.hooks.filterServiceEventReceivers(evt, receivers);

  foreach (CFSServiceSlotEntry l, receivers)
  {
    if (!matchBefore.isEmpty())
    {
      matchBefore.remove(l);
    }

    // TODO permission checks
    //if (l.bundle.hasPermission(new ServicePermission(sr, ServicePermission.GET))) {
    //foreach (QString clazz, classes)
    //{
    try
    {
      ++n;
      l.invokeSlot(evt);
    }
    catch (const CFSException& pe)
    {
      frameworkError(l.getPlugin(), pe);
    }
    catch (const std::exception& e)
    {
      frameworkError(l.getPlugin(), CFSRuntimeException(e.what()));
    }

    //break;
    //}
    //}
  }

  if (pluginFw->debug.ldap)
  {
    qDebug() << "Notified" << n << " listeners";
  }
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::removeFromCache(const CFSServiceSlotEntry& sse)
{
  if (!sse.getLocalCache().isEmpty())
  {
    for (int i = 0; i < hashedServiceKeys.size(); ++i)
    {
      QHash<QString, QList<CFSServiceSlotEntry> >& keymap = cache[i];
      QStringList& l = sse.getLocalCache()[i];
      QStringListIterator it(l);
      while (it.hasNext())
      {
        QString value = it.next();
        QList<CFSServiceSlotEntry>& sses = keymap[value];
        sses.removeAll(sse);
        if (sses.isEmpty())
        {
          keymap.remove(value);
        }
      }
    }
  }
  else
  {
    complicatedListeners.removeAll(sse);
  }
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::checkSimple(const CFSServiceSlotEntry& sse)
{
  if (sse.getLDAPExpr().isNull()) // || listeners.nocacheldap) {
  {
    complicatedListeners.push_back(sse);
  }
  else
  {
    CFSLDAPExpr::LocalCache local_cache;
    if (sse.getLDAPExpr().isSimple(hashedServiceKeys, local_cache, false))
    {
      sse.getLocalCache() = local_cache;
      for (int i = 0; i < hashedServiceKeys.size(); ++i)
      {
        QStringListIterator it(local_cache[i]);
        while (it.hasNext())
        {
          QString value = it.next();
          QList<CFSServiceSlotEntry>& sses = cache[i][value];
          sses.push_back(sse);
        }
      }
    }
    else
    {
      if (pluginFw->debug.ldap)
      {
        qDebug() << "## DEBUG: Too complicated filter:" << sse.getFilter();
      }
      complicatedListeners.push_back(sse);
    }
  }
}

//----------------------------------------------------------------------------
void CFSPluginFrameworkListeners::addToSet(QSet<CFSServiceSlotEntry>& set,
                                           int cache_ix, const QString& val)
{
  QList<CFSServiceSlotEntry>& l = cache[cache_ix][val];
  if (!l.isEmpty())
  {
    if (pluginFw->debug.ldap)
    {
      qDebug() << hashedServiceKeys[cache_ix] << "matches" << l.size();
    }
    foreach (CFSServiceSlotEntry entry, l)
    {
      set.insert(entry);
    }
  }
  else
  {
    if (pluginFw->debug.ldap)
    {
      qDebug() << hashedServiceKeys[cache_ix] << "matches none";
    }
  }
}
